using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;

namespace LS.GameKit
{
    public class PathTranslator : TransformerBehaviour
    {
        public bool usePhysicsBehaviour = true;
        public Space translateSpace = Space.Self;
        public Transform activedTransform;
        public PhysicsBehaviour activedPhysicsBehaviour;

        [SerializeField]
        private List<Vector3> path = new List<Vector3>();

        private PathRoute m_PathRoute = new PathRoute();

        protected override void Start()
        {
            InitPathRoute();
        }

        public void InitPathRoute()
        {
            if (m_PathRoute == null)
                m_PathRoute = new PathRoute();
            m_PathRoute.Setup(path);
        }

        public override void PerformTransform(float position)
        {
            var curvePosition = accelCurve.Evaluate(position);
            Vector3 pos = m_PathRoute.GetPoint(curvePosition);
            if (translateSpace == Space.Self)
                pos = LocalToWorldPosition(pos);
            Vector3 delta = pos - GetPosition();
            Translate(delta);
            this.OnDeltaPosition?.Invoke(delta);
            this.OnPositionChanged?.Invoke(pos);
        }

        public void Translate(Vector3 delta)
        {
            Translate(delta, Space.World);
        }

        public void Translate(Vector3 delta, Space space)
        {
            if (usePhysicsBehaviour)
            {
                if (space == Space.Self)
                    activedPhysicsBehaviour.TranslateLocal(delta);
                else
                    activedPhysicsBehaviour.Translate(delta);
            }
            else
            {
                if (space == Space.Self)
                    activedTransform.Translate(delta, Space.Self);
                else
                    activedTransform.Translate(delta, Space.World);
            }
        }

        public void SetPosition(Vector3 position, Space space)
        {
            if (usePhysicsBehaviour && activedPhysicsBehaviour != null)
            {
                if (space == Space.Self)
                    activedPhysicsBehaviour.LocalPosition = position;
                else
                    activedPhysicsBehaviour.Position = position;
            }
            else
            {
                if (space == Space.Self)
                    activedTransform.localPosition = position;
                else
                    activedTransform.position = position;
            }
        }

        public void SetPosition(Vector3 position)
        {
            SetPosition(position, Space.World);
        }

        public Vector3 GetPosition()
        {
            return GetPosition(Space.World);
        }

        public Vector3 GetPosition(Space space)
        {
            if (usePhysicsBehaviour && activedPhysicsBehaviour != null)
            {
                if (space == Space.Self)
                    return activedPhysicsBehaviour.LocalPosition;
                else
                    return activedPhysicsBehaviour.Position;
            }
            else
            {
                if (space == Space.Self)
                    return activedTransform.localPosition;
                else
                    return activedTransform.position;
            }
        }

        private Vector3 LocalToWorldPosition(Vector3 position)
        {
            if (usePhysicsBehaviour && activedPhysicsBehaviour != null)
            {
                return activedPhysicsBehaviour.CachedTransform.parent.TransformPoint(position);
            }
            else
            {
                return activedTransform.parent.TransformPoint(position);
            }
        }

        protected virtual void Reset()
        {
            activedPhysicsBehaviour = GetComponentInChildren<PhysicsBehaviour>();
            if (activedPhysicsBehaviour != null)
            {
                activedTransform = activedPhysicsBehaviour.transform;
                return;
            }

            if (transform.childCount > 0)
            {
                activedTransform = transform.GetChild(0);
            }
            else activedTransform = transform;
        }

#if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            Color col = Color.blue;
            col.a = 0.75f;
            Gizmos.color = col;
            for (int i = 0; i < m_PathRoute.path.Count; i++)
            {
                Vector3 tstart = m_PathRoute.path[i].Start;
                Vector3 tend = m_PathRoute.path[i].End;

                if (translateSpace == Space.Self)
                {
                    tstart = transform.TransformPoint(tstart);
                    tend = transform.TransformPoint(tend);
                }
                Gizmos.DrawLine(tstart, tend);
                Gizmos.DrawCube(tstart, Vector3.one * 0.2f);
                Gizmos.DrawCube(tend, Vector3.one * 0.2f);
            }

            var curvePosition = accelCurve.Evaluate(previewPosition);
            var pos = m_PathRoute.GetPoint(curvePosition);
            if (translateSpace == Space.Self)
                pos = transform.TransformPoint(pos);
            col = Color.cyan;
            col.a = 0.75f;
            Gizmos.color = col;
            Gizmos.DrawCube(pos, Vector3.one);
        }
#endif

        public class PathRoute
        {
            private float m_Distance;

            public List<PathLine> path = new List<PathLine>();

            public float Distance => m_Distance;

            public PathRoute() { }

            public PathRoute(List<Vector3> nodes)
            {
                Setup(nodes);
            }

            public PathRoute(Vector3[] nodes)
            {
                Setup(nodes);
            }

            public void Setup(List<Vector3> nodes)
            {
                path.Clear();
                if (nodes.Count <= 0)
                {
                    return;
                }

                if (nodes.Count < 2)
                {
                    path.Add(new PathLine(nodes[0], nodes[0]));
                    m_Distance = 0;
                    return;
                }

                for (int i = 1; i < nodes.Count; i++)
                {
                    var line = new PathLine(nodes[i - 1], nodes[i]);
                    m_Distance += line.Distance;
                    path.Add(line);
                }
            }

            public void Setup(Vector3[] nodes)
            {
                path.Clear();
                if (nodes.Length <= 0)
                {
                    return;
                }

                if (nodes.Length < 2)
                {
                    path.Add(new PathLine(nodes[0], nodes[0]));
                    m_Distance = 0;
                    return;
                }

                for (int i = 1; i < nodes.Length; i++)
                {
                    var line = new PathLine(nodes[i - 1], nodes[i]);
                    m_Distance += line.Distance;
                    path.Add(line);
                }
            }

            public Vector3 GetPoint(float lerp)
            {
                if (path.Count == 0) return Vector3.zero;

                var targetDistance = Mathf.Lerp(0, Distance, lerp);
                float tempDis = 0;
                int index = 0;
                PathLine line = null;
                for (int i = 0; i < path.Count; i++)
                {
                    tempDis += path[i].Distance;
                    index = i;
                    line = path[i];
                    if (tempDis > targetDistance)
                    {
                        break;
                    }
                }
                float targetLineDis = line.Distance - (tempDis - targetDistance);
                return line.GetPoint(targetLineDis / line.Distance);
            }

            public float Projection(Vector3 point)
            {
                if (path.Count <= 0) return 0;
                if (path.Count == 1) return path[0].Projection(point);

                NativeArray<float4> projections = new NativeArray<float4>(path.Count, Allocator.TempJob);
                NativeArray<float3x2> lines = new NativeArray<float3x2>(path.Count, Allocator.TempJob);
                NativeReference<float3> pointRef = new NativeReference<float3>(Allocator.TempJob);
                NativeReference<int> indexRef = new NativeReference<int>(Allocator.TempJob);
                for (int i = 0; i < path.Count; i++)
                {
                    lines[i] = new float3x2()
                    {
                        c0 = path[i].Start,
                        c1 = path[i].End
                    };
                }

                pointRef.Value = point;
                indexRef.Value = 0;

                var projectJob = new ProjectionPathDistanceJob()
                {
                    targetPoint = pointRef,
                    projections = projections,
                    lines = lines
                };

                var projectPathJob = new ProjectionPathJob()
                {
                    index = indexRef,
                    projections = projections
                };
                var pd = projectJob.Schedule(path.Count, 1);
                var pj = projectPathJob.Schedule(pd);
                pj.Complete();
                int index = indexRef.Value;
                projections.Dispose();
                lines.Dispose();
                pointRef.Dispose();
                indexRef.Dispose();

                var line = path[index];
                return line.Projection(point);
            }

            public void Clear()
            {
                path.Clear();
            }
        }

        public class PathLine
        {
            private float m_Distance;
            private Vector3 m_Start;
            private Vector3 m_End;

            public Vector3 Start => m_Start;
            public Vector3 End => m_End;
            public float Distance => m_Distance;

            public PathLine(Vector3 start, Vector3 end)
            {
                m_Start = start;
                m_End = end;
                m_Distance = Vector3.Distance(start, end);
            }

            /// <summary>
            /// 获取点位置
            /// </summary>
            /// <param name="lerp">输入获取比例</param>
            /// <returns></returns>
            public Vector3 GetPoint(float lerp)
            {
                if (Start == End) return Start;
                return Vector3.Lerp(Start, End, lerp);
            }

            /// <summary>
            /// 获取投影坐标
            /// </summary>
            /// <param name="point">坐标信息</param>
            /// <returns></returns>
            public float Projection(Vector3 point)
            {
                if (Start == End) return 0;

                Vector3 dir = Vector3.Normalize(point - Start);
                Vector3 normal = (End - Start).normalized;
                Vector3 p = Vector3.Project(dir, normal);
                return p.magnitude / Distance;
            }
        }

        /// <summary>
        /// 计算路径投影点
        /// </summary>
        public struct ProjectionPathJob : IJob
        {
            [ReadOnly]
            public NativeArray<float4> projections;

            public NativeReference<int> index;

            public void Execute()
            {
                int index = 0;
                for (int i = 0; i < projections.Length; i++)
                {
                    if (projections[i].w < projections[index].w)
                    {
                        index = i;
                    }
                }
                this.index.Value = index;
            }
        }

        /// <summary>
        /// 计算距离最近的线段投影点工作
        /// </summary>
        public struct ProjectionPathDistanceJob : IJobParallelFor
        {
            [ReadOnly]
            public NativeArray<float3x2> lines;

            /// <summary>
            /// xyz是Vector3值，w是坐标点到投影点的距离
            /// </summary>
            public NativeArray<float4> projections;

            [ReadOnly]
            public NativeReference<float3> targetPoint;

            public void Execute(int index)
            {
                float3 dir = targetPoint.Value - lines[index].c0;
                float3 normal = math.normalize(lines[index].c1 - lines[index].c0);
                float3 project = math.project(dir, normal);
                float3 pPoint = project + lines[index].c0;
                projections[index] = new float4(pPoint, math.distance(targetPoint.Value, pPoint));
            }
        }
    }
}
